package xyz.app.queries;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import lombok.Getter;
import lombok.NonNull;
import lombok.experimental.Accessors;
import xyz.app.accessors.DbReader;
import xyz.app.dependencies.HasDomainMembers;
import xyz.app.AbstractDomain;

import java.util.Collection;
import java.util.Map;
import java.util.Optional;

@Getter @Accessors(fluent = true)
public abstract class CriteriaWrapperQuery<T extends AbstractDomain, V> implements
        EntityGraphCreator<T>,
        WhereEqualsOrNull<T> {

    private final Class<V> resultClass;
    private final DbReader<T> reader;

    private final CriteriaBuilder builder;
    private final Root<T> root;
    private final CriteriaQuery<V> criteria;

    private CriteriaWrapperQuery(@NonNull DbReader<T> reader, @NonNull Class<V> resultClass) {
        this.resultClass = resultClass;
        this.reader = reader;

        builder = reader.getEmf().getCriteriaBuilder();
        criteria = builder.createQuery(resultClass);
        root = criteria.from(reader.domainClass());
    }

    public @NonNull Class<T> domainClass() {
        return reader.domainClass();
    }

    @Override
    public Optional<HasDomainMembers<?>> hasMembers() {
        return reader instanceof HasDomainMembers<?> hm ? Optional.of(hm) : Optional.empty();
    }

    @Override
    public <U> @NonNull CriteriaWrapperQuery<T, V> whereEqualsOrNull(@NonNull String name, U value) {
        criteria.where(getPredicateEqualsOrNull(name, value));
        return this;
    }

    @Override
    public @NonNull CriteriaWrapperQuery<T, V> whereEqualsOrNull(@NonNull Map<String, Object> values) {
        criteria.where(getPredicateEqualsOrNull(values));
        return this;
    }

    public @NonNull Collection<V> getResultList() {
        try (var manager = reader.getEmf().createEntityManager()) {
            var query = manager.createQuery(criteria);
            createGraph(manager, query);
            return query.getResultList();
        }
    }

    public Optional<V> getSingleResult() {
        var resultList = getResultList();
        return resultList.isEmpty() ? Optional.empty() : Optional.of(resultList.iterator().next());
    }

    public static class OneClass<T extends AbstractDomain> extends CriteriaWrapperQuery<T, T> {

        public OneClass(@NonNull DbReader<T> reader) {
            super(reader, reader.domainClass());
        }

        @Override
        public <U> @NonNull OneClass<T> whereEqualsOrNull(@NonNull String name, U value) {
            super.whereEqualsOrNull(name, value);
            return this;
        }

        @Override
        public @NonNull OneClass<T> whereEqualsOrNull(@NonNull Map<String, Object> values) {
            super.whereEqualsOrNull(values);
            return this;
        }
    }
}